package AnalizadorSemantico;

import java.awt.BorderLayout;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeModel;

import AnalizadorLexico.CategoriaLexica;
import AnalizadorLexico.Lexema;
import AnalizadorSintactico.Campo;
import AnalizadorSintactico.DeclaracionVariable;
import AnalizadorSintactico.Expresion;
import AnalizadorSintactico.Metodo;
import AnalizadorSintactico.PalabraReservada;
import AnalizadorSintactico.Sentencia;
import AnalizadorSintactico.UnidadCompilacion;

public class AnalizadorSemantico implements PalabraReservada, CategoriaLexica {

	private UnidadCompilacion unidadCompilacion;
	private ArrayList<SimboloCampo> tablaCampos;
	private ArrayList<SimboloMetodo> tablaMetodos;
	private ArrayList<String> tablaErrores;

	public AnalizadorSemantico(UnidadCompilacion unidadCompilacion) {
		this.unidadCompilacion = unidadCompilacion;
		tablaCampos = new ArrayList<SimboloCampo>();
		tablaMetodos = new ArrayList<SimboloMetodo>();
		tablaErrores = new ArrayList<String>();
	}

	public void analizar() {
		try {
			analizarCampos();
			analizarMetodos();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void analizarMetodos() {
		ArrayList<Metodo> metodos = unidadCompilacion
				.getDeclaracionClaseInterfaz().getClase().getBloqueClase()
				.getBloqueMetodos();
		for (Metodo metodo : metodos) {
			Lexema nombre = metodo.getIdentificador();
			Lexema acceso = metodo.getAcceso();
			Lexema modificador = metodo.getModificador();
			Lexema retorno = metodo.getTipoRetorno();
			ArrayList<String> parametros = metodo.getParametros();
			ArrayList<Sentencia> listaSentencias = metodo.getBloqueSentencias();

			// si el metodo no ha sido declarado aun se agrega a la lista y se
			// analiza su contenido
			if (agregarMetodo(new SimboloMetodo(nombre, retorno, acceso,
					modificador, parametros))) {

				// analizar las sentencias de cada metodo
				for (Sentencia sentencia : listaSentencias) {
					Class<? extends Sentencia> n = sentencia.getClass();
					// si es declaracion de variable
					if (n.getCanonicalName().equals(
							"AnalizadorSintactico.DeclaracionVariable")) {
						DeclaracionVariable d = ((DeclaracionVariable) sentencia);
						Lexema tipo = d.getTipo();
						Lexema variable = d.getVariable();
						String ambito = d.getAmbito();
						Expresion exp = d.getExpresion();
						if (agregarCampo(new SimboloCampo(variable, tipo, null,
								ambito))) {
							exp.setAmbito(ambito);
							exp.setTablaCampos(tablaCampos);
							exp.setTablaMetodos(tablaMetodos);
							String tipoExpresion = exp.evaluarTipoResultante();

							if (exp != null) {
								JPanel panel = new JPanel();
								JScrollPane scrpn = new JScrollPane(panel);
								panel.setLayout(new BorderLayout());
								DefaultTreeModel modelo = new DefaultTreeModel(
										exp.getSubArbol());
								JTree arbol = new JTree(modelo);
								panel.add(arbol);
								JFrame ventana = new JFrame();
								ventana.setSize(600, 400);
								ventana.setTitle("variable: "
										+ variable.getLexema() + "   ambito: "
										+ ambito + " tipoResultante: "
										+ exp.evaluarTipoResultante());
								ventana.setContentPane(scrpn); //
								// ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
								ventana.setLocationRelativeTo(null);
								ventana.setVisible(true);
							}

						}
					}
				}
			} else {
				continue;
			}
		}
	}

	/*
	 * Comprueba si el metodo esta o no declarado aun en la tabla de metodos, si
	 * esta declarado comprueba los parametros para ver si es una sobrecarga de
	 * metodos
	 */
	private boolean agregarMetodo(SimboloMetodo metodo) {
		boolean esDuplicado = false;
		for (SimboloMetodo simMet : tablaMetodos) {

			if (simMet.getNombre().getLexema().equals(
					metodo.getNombre().getLexema())) {
				if (simMet.getParametros().equals(metodo.getParametros())) {
					reportarError("metodo duplicado  "
							+ simMet.getNombre().getLexema());
					esDuplicado = true;
				}
			}
		}
		// si no es duplicado lo agrega a la tabla si si continua con el
		// siguiente metodo
		if (esDuplicado == false) {
			tablaMetodos.add(metodo);
			return true;
		} else {
			return false;
		}
	}

	private void analizarCampos() {
		ArrayList<Campo> campos = unidadCompilacion
				.getDeclaracionClaseInterfaz().getClase().getBloqueClase()
				.getBloqueCampos();
		for (Campo campo : campos) {
			Lexema acceso = campo.getAcceso();
			Lexema modificador = campo.getModificador();
			Lexema nombre = campo.getIdCampo();
			Lexema tipo = campo.getTipo();
			Lexema valor = campo.getValor();
			String ambito = campo.getAmbito();

			// comprueba si el campo ya existe, si no lo agrega y analiza el
			// valor asignado
			if (agregarCampo(new SimboloCampo(nombre, tipo, acceso, ambito))) {
				if (!UtilidadesSemanticas.correspondenciaTipos(tipo.getTipo(),
						valor.getTipo())) {
					reportarError("tipos incompatibles  campo "
							+ nombre.getLexema() + ":  " + valor.getTipo()
							+ " no puede ser asignado a " + tipo.getTipo());
				}
			} else {
				continue;
			}
		}
	}

	/*
	 * comprueba si el campo existe, si no lo agrega a la tabla de campos
	 * @return boolean true si se agrego false si no
	 */
	private boolean agregarCampo(SimboloCampo campo) {
		boolean yaExiste = false;
		for (SimboloCampo simCam : tablaCampos) {
			if ((simCam.getNombre().getLexema().equals(campo.getNombre()
					.getLexema()))
					&& (simCam.getAmbito().equals(campo.getAmbito()))) {
				reportarError("Identificador ya declarado  "
						+ simCam.getNombre().getLexema());
				yaExiste = true;
			}
		}
		if (yaExiste == false) {
			tablaCampos.add(campo);
			return true;
		} else {
			return false;
		}
	}

	private void reportarError(String error) {
		tablaErrores.add(error + "\n");
	}

	public ArrayList<String> getErrores() {
		return tablaErrores;
	}
}
